﻿Imports System.IO
Imports System.Data.OleDb
Imports Microsoft.ML
Imports Microsoft.ML.Data

Public Class Tera計算EIQAIFrom
＃Region "変数処理"
    ' ========================================
    ' クラスレベル変数
    ' ========================================
    Private mlContext As MLContext
    Private trainedModels As Dictionary(Of String, ITransformer)
    Private modelFolder As String
    Private 出力フィールド名リスト As List(Of String)
    ' マトリクス表が有効になっているか（EIQマトリクス表実行Bt が押された後に True）
    Private matrixActive As Boolean = False

    ' ヘルパー: 文字列を四捨五入して整数文字列にする
    Private Function ToRoundedString(s As String) As String
        If String.IsNullOrWhiteSpace(s) Then
            Return String.Empty
        End If
        Dim d As Double
        If Double.TryParse(s, Globalization.NumberStyles.Any, Globalization.CultureInfo.InvariantCulture, d) Then
            Return CInt(Math.Round(d)).ToString()
        ElseIf Double.TryParse(s, Globalization.NumberStyles.Any, Globalization.CultureInfo.CurrentCulture, d) Then
            Return CInt(Math.Round(d)).ToString()
        Else
            Return String.Empty
        End If
    End Function

    ' T710 テーブル作成: T410 のロジックと同様に乱数に基づいて判定テーブルを作成する
    Private Sub T710テーブル作成(tableName As String, 採用比率 As Double, seed As Integer)
        If String.IsNullOrEmpty(MyDB) OrElse Not File.Exists(MyDB) Then Throw New Exception("DBファイルが見つかりません")

        Using conn As New OleDbConnection($"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={MyDB}")
            conn.Open()

            ' テーブル削除（存在する場合）
            Using cmdDrop As New OleDbCommand($"DROP TABLE [{tableName}]", conn)
                Try
                    cmdDrop.ExecuteNonQuery()
                Catch
                    ' 無視
                End Try
            End Using

            ' 採用比率が1以下ならT410テーブル作成1の方式（乱数を生成）
            If 採用比率 <= 1 Then
                ' シードに基づく乱数生成で指定件数分のレコードを作成する。
                ' 件数は T160 の順位最大値を利用。
                Dim cnt As Integer
                If tableName.Contains("_E") Then
                    cnt = GetMaxRankFromTable("T160_全出荷E集計")
                Else
                    cnt = GetMaxRankFromTable("T160_全出荷I集計")
                End If

                Dim rnd As New Random(seed)

                ' CREATE TABLE
                Dim createSql As String
                If tableName.Contains("_E") Then
                    createSql = $"CREATE TABLE [{tableName}] (順位 AUTOINCREMENT PRIMARY KEY, 乱数 DOUBLE, 判定 VARCHAR(10), 出荷先名 VARCHAR(255))"
                Else
                    createSql = $"CREATE TABLE [{tableName}] (順位 AUTOINCREMENT PRIMARY KEY, 乱数 DOUBLE, 判定 VARCHAR(10), アイテム VARCHAR(255))"
                End If
                Using cmdCreate As New OleDbCommand(createSql, conn)
                    cmdCreate.ExecuteNonQuery()
                End Using

                ' INSERT
                Dim insertSql As String = $"INSERT INTO [{tableName}] (乱数, 判定) VALUES (?, ?)"
                Using cmdIns As New OleDbCommand(insertSql, conn)
                    cmdIns.Parameters.Add("@乱数", OleDbType.Double)
                    cmdIns.Parameters.Add("@判定", OleDbType.VarChar, 10)
                    For i As Integer = 1 To cnt
                        Dim rv As Double = rnd.NextDouble()
                        Dim 判定 As String = If(rv <= 採用比率, "Yes", "No")
                        cmdIns.Parameters("@乱数").Value = rv
                        cmdIns.Parameters("@判定").Value = 判定
                        cmdIns.ExecuteNonQuery()
                    Next
                End Using

                ' T160 から名称を取り込む
                Dim updateSql As String
                If tableName.Contains("_E") Then
                    updateSql = $"UPDATE [{tableName}] LEFT JOIN T160_全出荷E集計 ON [{tableName}].順位 = T160_全出荷E集計.順位 SET [{tableName}].出荷先名 = [T160_全出荷E集計]![出荷先];"
                Else
                    updateSql = $"UPDATE [{tableName}] LEFT JOIN T160_全出荷I集計 ON [{tableName}].順位 = T160_全出荷I集計.順位 SET [{tableName}].アイテム = [T160_全出荷I集計]![アイテム];"
                End If
                Using cmdUpd As New OleDbCommand(updateSql, conn)
                    cmdUpd.ExecuteNonQuery()
                End Using

            Else
                ' 採用比率 > 1 の場合は T410作成2 の方式（T160 の乱数をそのまま取り込む）
                Dim src As String = If(tableName.Contains("_E"), "T160_全出荷E集計", "T160_全出荷I集計")
                Dim fld As String = If(tableName.Contains("_E"), "出荷先", "アイテム")
                Dim tname As String = If(tableName.Contains("_E"), "出荷先名", "アイテム")

                Dim createSelectSql As String = $"SELECT 順位, 乱数, IIF(乱数 <= {採用比率 - 1}, 'Yes', 'No') AS 判定, {fld} AS {tname} INTO [{tableName}] FROM {src}"
                Using cmd As New OleDbCommand(createSelectSql, conn)
                    cmd.ExecuteNonQuery()
                End Using
            End If
        End Using
    End Sub

    ' T900 の集計結果を UI の実計算Gb のテキストと 実計算Dgv に反映する
    Private Sub Update実計算表示FromT900()
        If String.IsNullOrEmpty(MyDB) OrElse Not File.Exists(MyDB) Then Return

        Using conn As New OleDbConnection($"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={MyDB}")
            conn.Open()

            ' T900_データモデル が存在するか確認
            Dim schema = conn.GetSchema("Tables")
            Dim found As Boolean = False
            For Each row As DataRow In schema.Rows
                If row("TABLE_NAME").ToString().ToUpper() = "T900_データモデル".ToUpper() Then
                    found = True
                    Exit For
                End If
            Next
            If Not found Then Return

            ' 単一行を取得
            Dim sql As String = "SELECT * FROM T900_データモデル"
            Dim dt As New DataTable()
            Using da As New OleDbDataAdapter(sql, conn)
                da.Fill(dt)
            End Using
            If dt.Rows.Count = 0 Then Return

            Dim r As DataRow = dt.Rows(0)

            ' 基本フィールドをテキストボックスに設定
            Try
                TextBox27.Text = If(dt.Columns.Contains("出荷先数") AndAlso Not IsDBNull(r("出荷先数")), Convert.ToDouble(r("出荷先数")).ToString("F0"), "")
            Catch
            End Try
            Try
                TextBox26.Text = "アイテム数"
                TextBox17.Text = If(dt.Columns.Contains("アイテム数") AndAlso Not IsDBNull(r("アイテム数")), Convert.ToDouble(r("アイテム数")).ToString("F0"), "")
            Catch
            End Try

            ' pred 辞書を作成（予測側のロジックと同様のキー）
            Dim pred As New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
            ' E1..E5, I1..I5
            For i As Integer = 1 To 5
                Dim ec = $"E{i}"
                If dt.Columns.Contains(ec) AndAlso Not IsDBNull(r(ec)) Then pred(ec) = Convert.ToDouble(r(ec)).ToString("F0") Else pred(ec) = "0"
                Dim ic = $"I{i}"
                If dt.Columns.Contains(ic) AndAlso Not IsDBNull(r(ic)) Then pred(ic) = Convert.ToDouble(r(ic)).ToString("F0") Else pred(ic) = "0"
            Next
            ' 行数01-25, バラ数01-25, ケース01-25, PL01-25, 容積01-25, 重量01-25
            Dim prefixes As String() = {"行数", "バラ数", "ケース", "PL", "容積", "重量"}
            For Each pfx As String In prefixes
                For i As Integer = 1 To 25
                    Dim colName = pfx & i.ToString("D2")
                    If dt.Columns.Contains(colName) AndAlso Not IsDBNull(r(colName)) Then
                        pred(colName) = Convert.ToDouble(r(colName)).ToString("F0")
                    Else
                        pred(colName) = "0"
                    End If
                Next
            Next

            ' 実計算Gb のテキストボックスに E1..E5 を設定（左縦）
            Try
                TextBox25.Text = If(pred.ContainsKey("E1"), pred("E1"), "")
                TextBox24.Text = If(pred.ContainsKey("E2"), pred("E2"), "")
                TextBox21.Text = If(pred.ContainsKey("E3"), pred("E3"), "")
                TextBox30.Text = If(pred.ContainsKey("E4"), pred("E4"), "")
                TextBox18.Text = If(pred.ContainsKey("E5"), pred("E5"), "")
                ' 合計（左下）
                Dim sumE As Long = 0
                For i As Integer = 1 To 5
                    sumE += ToLongOrZero(If(pred.ContainsKey($"E{i}"), pred($"E{i}"), "0"))
                Next
                TextBox23.Text = sumE.ToString()
            Catch
            End Try

            ' 実計算Gb のテキストボックスに I1..I5 を設定（上横）
            Try
                TextBox17.Text = If(pred.ContainsKey("I1"), pred("I1"), "")
                TextBox31.Text = If(pred.ContainsKey("I2"), pred("I2"), "")
                TextBox19.Text = If(pred.ContainsKey("I3"), pred("I3"), "")
                TextBox29.Text = If(pred.ContainsKey("I4"), pred("I4"), "")
                TextBox20.Text = If(pred.ContainsKey("I5"), pred("I5"), "")
                Dim sumI As Long = 0
                For i As Integer = 1 To 5
                    sumI += ToLongOrZero(If(pred.ContainsKey($"I{i}"), pred($"I{i}"), "0"))
                Next
                TextBox32.Text = sumI.ToString()
            Catch
            End Try

            ' マトリクス（実計算Dgv）を予測計算2Gb と同様のフォーマットで作成
            If 実計算Dgv Is Nothing Then Return
            実計算Dgv.Rows.Clear()
            実計算Dgv.Columns.Clear()
            実計算Dgv.Columns.Add("GPLEランク", "GPLEランク")
            For c As Integer = 1 To 5
                実計算Dgv.Columns.Add($"GPLI_{c}", $"GPLI_{c}")
            Next
            実計算Dgv.Columns.Add("横計", "横計")

            Dim labelsArr As String() = {"GPLE_A1", "GPLE_A2", "GPLE_B", "GPLE_C", "GPLE_D"}
            For Each lbl As String In labelsArr
                実計算Dgv.Rows.Add(New Object() {lbl, "", "", "", "", "", ""})
            Next

            ' 表示単位プレフィックスを決定（表示チェック2Gb のラジオボタンに同期）
            Dim prefixSel As String = ""
            If 抽出_出庫回数2Rb.Checked Then prefixSel = "行数"
            If 抽出_バラ2Rb.Checked Then prefixSel = "バラ数"
            If 抽出_ケース2Rb.Checked Then prefixSel = "ケース"
            If 抽出_PL2Rb.Checked Then prefixSel = "PL"
            If 抽出_容積2Rb.Checked Then prefixSel = "容積"
            If 抽出_重量2Rb.Checked Then prefixSel = "重量"

            Const dataRows As Integer = 5
            Const dataCols As Integer = 5
            Dim lastColIndex As Integer = 実計算Dgv.Columns.Count - 1
            Dim totalsRowIndex As Integer = dataRows

            ' 埋め込み
            For rIdx As Integer = 0 To dataRows - 1
                For cIdx As Integer = 1 To dataCols
                    Dim idx As Integer = rIdx * dataCols + (cIdx - 1)
                    Dim k As Integer = idx + 1
                    Dim fieldName As String = prefixSel & k.ToString("D2")
                    Dim value As String = ""
                    If pred.ContainsKey(fieldName) Then value = pred(fieldName)
                    実計算Dgv.Rows(rIdx).Cells(cIdx).Value = value
                Next
            Next

            ' 横計（各行）
            For rIdx As Integer = 0 To dataRows - 1
                Dim rowSum As Long = 0
                For cIdx As Integer = 1 To dataCols
                    rowSum += ToLongOrZero(If(実計算Dgv.Rows(rIdx).Cells(cIdx).Value, "").ToString())
                Next
                実計算Dgv.Rows(rIdx).Cells(lastColIndex).Value = rowSum.ToString()
            Next

            ' 縦計（各列）
            For cIdx As Integer = 1 To dataCols
                Dim colSum As Long = 0
                For rIdx As Integer = 0 To dataRows - 1
                    colSum += ToLongOrZero(If(実計算Dgv.Rows(rIdx).Cells(cIdx).Value, "").ToString())
                Next
                ' ensure totals row exists
                If 実計算Dgv.Rows.Count <= totalsRowIndex Then
                    実計算Dgv.Rows.Add()
                End If
                実計算Dgv.Rows(totalsRowIndex).Cells(cIdx).Value = colSum.ToString()
            Next

            ' 最下行左のラベルと総合計
            If 実計算Dgv.Rows.Count <= totalsRowIndex Then 実計算Dgv.Rows.Add()
            実計算Dgv.Rows(totalsRowIndex).Cells(0).Value = "縦計"
            Dim grandTotal As Long = 0
            For cIdx As Integer = 1 To dataCols
                grandTotal += ToLongOrZero(If(実計算Dgv.Rows(totalsRowIndex).Cells(cIdx).Value, "").ToString())
            Next
            実計算Dgv.Rows(totalsRowIndex).Cells(lastColIndex).Value = grandTotal.ToString()
            実計算Dgv.ReadOnly = True
            実計算Dgv.RowHeadersVisible = False
            実計算Dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells
        End Using
    End Sub

    ' T800_手計算対象作成: T500_計算対象作成 と同様のロジックで T800 を作成
    Private Sub T800_手計算対象作成(出荷先比率 As Double, アイテム比率 As Double)
        If String.IsNullOrEmpty(MyDB) OrElse Not File.Exists(MyDB) Then Throw New Exception("DBファイルが見つかりません")

        Using conn As New OleDbConnection($"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={MyDB}")
            conn.Open()

            ' 既存の T800_手計算対象 を削除
            Using cmdDrop As New OleDbCommand("DROP TABLE [T800_手計算対象]", conn)
                Try
                    cmdDrop.ExecuteNonQuery()
                Catch
                End Try
            End Using

            ' T500 作成と同じ結合ロジックを使用して T800_手計算対象 を作成
            ' (T410 -> T500 の流れに相当するため、T710_E判定/T710_I判定 を使用)
            Dim sqlCreate As String = "SELECT T200.* INTO T800_手計算対象 " &
                "FROM (T710_E判定 RIGHT JOIN T200 ON T710_E判定.出荷先名 = T200.出荷先) " &
                "LEFT JOIN T710_I判定 ON T200.[アイテム] = T710_I判定.[アイテム] " &
                "WHERE (((T710_E判定.判定)='Yes') AND ((T710_I判定.判定)='Yes'));"

            Using cmdCreate As New OleDbCommand(sqlCreate, conn)
                cmdCreate.ExecuteNonQuery()
            End Using

            ' T410/採用範囲Chk 相当の処理: T410作成2と同様に、もし採用比率が >1 の場合は語尾に"A"付加し、必要ならT200のデータを追加
            ' ここでは T710 テーブル作成で E/I 採用範囲Chk のフラグを保持していないため、簡易的に比率>1 を判定して処理
            If 出荷先比率 > 1 Then
                Using cmdUpd As New OleDbCommand("UPDATE T800_手計算対象 SET 出荷先 = 出荷先 & 'A'", conn)
                    Try
                        cmdUpd.ExecuteNonQuery()
                    Catch
                    End Try
                End Using
            End If
            If アイテム比率 > 1 Then
                Using cmdUpd As New OleDbCommand("UPDATE T800_手計算対象 SET アイテム = アイテム & 'A'", conn)
                    Try
                        cmdUpd.ExecuteNonQuery()
                    Catch
                    End Try
                End Using
            End If

            If 出荷先比率 > 1 OrElse アイテム比率 > 1 Then
                Using cmdIns As New OleDbCommand("INSERT INTO T800_手計算対象 SELECT * FROM T200", conn)
                    Try
                        cmdIns.ExecuteNonQuery()
                    Catch
                    End Try
                End Using
            End If
        End Using
    End Sub

    ' T160 の順位最大値を取得するためのラッパー
    Private Function GetMaxRankFromTable(tableName As String) As Integer
        ' テーブルの数値カラムの最大値を利用
        Return GetMaxNumericValueFromTable(tableName)
    End Function

    ' 指定の DataTable を DB にテーブルとして作成または置換する簡易実装
    Private Sub CreateOrReplaceTableFromDataTable(conn As OleDbConnection, tableName As String, dt As DataTable)
        Try
            Using cmd As New OleDbCommand($"DROP TABLE [{tableName}]", conn)
                Try
                    cmd.ExecuteNonQuery()
                Catch ex As Exception
                    ' 存在しない場合は無視
                End Try
            End Using

            ' CREATE TABLE 文を組み立て
            Dim cols As New List(Of String)()
            For Each col As DataColumn In dt.Columns
                Dim colDef As String = $"[{col.ColumnName}] "
                If col.DataType Is GetType(Integer) OrElse col.DataType Is GetType(Long) Then
                    colDef &= "LONG"
                ElseIf col.DataType Is GetType(Double) OrElse col.DataType Is GetType(Single) OrElse col.DataType Is GetType(Decimal) Then
                    colDef &= "DOUBLE"
                Else
                    colDef &= "TEXT"
                End If
                cols.Add(colDef)
            Next

            Dim createSql As String = $"CREATE TABLE [{tableName}] (" & String.Join(",", cols) & ")"
            Using cmdCreate As New OleDbCommand(createSql, conn)
                cmdCreate.ExecuteNonQuery()
            End Using

            ' バルクインサート
            For Each r As DataRow In dt.Rows
                Dim names As New List(Of String)()
                Dim vals As New List(Of String)()
                For Each c As DataColumn In dt.Columns
                    names.Add($"[{c.ColumnName}]")
                    If IsDBNull(r(c)) Then
                        vals.Add("NULL")
                    ElseIf c.DataType Is GetType(String) Then
                        vals.Add($"'{r(c).ToString().Replace("'", "''")}'")
                    Else
                        vals.Add(r(c).ToString())
                    End If
                Next
                Dim ins As String = $"INSERT INTO [{tableName}] (" & String.Join(",", names) & ") VALUES (" & String.Join(",", vals) & ")"
                Using cmdIns As New OleDbCommand(ins, conn)
                    cmdIns.ExecuteNonQuery()
                End Using
            Next

        Catch ex As Exception
            Throw
        End Try
    End Sub

    Private Function ToLongOrZero(s As String) As Long
        Dim v As Long
        If Long.TryParse(s, v) Then
            Return v
        End If
        Return 0
    End Function

    ' DBヘルパー: 指定テーブルの数値カラムの最大値を取得する
    Private Function GetMaxNumericValueFromTable(tableName As String) As Integer
        Try
            If String.IsNullOrEmpty(MyDB) OrElse Not File.Exists(MyDB) Then Return 0

            Using conn As New OleDbConnection($"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={MyDB}")
                conn.Open()

                ' テーブル存在確認
                Dim schema = conn.GetSchema("Tables")
                Dim found As Boolean = False
                For Each row As DataRow In schema.Rows
                    Dim tname = row("TABLE_NAME").ToString()
                    Dim ttype = row("TABLE_TYPE").ToString()
                    If ttype = "TABLE" AndAlso String.Compare(tname, tableName, True) = 0 Then
                        found = True
                        Exit For
                    End If
                Next
                If Not found Then Return 0

                Dim da As New OleDbDataAdapter($"SELECT * FROM [{tableName}]", conn)
                Dim dt As New DataTable()
                da.Fill(dt)
                If dt.Rows.Count = 0 Then Return 0

                Dim maxVal As Double = Double.MinValue
                For Each col As DataColumn In dt.Columns
                    Dim t = col.DataType
                    If t Is GetType(Integer) OrElse t Is GetType(Long) OrElse t Is GetType(Double) OrElse t Is GetType(Single) OrElse t Is GetType(Decimal) Then
                        For Each r As DataRow In dt.Rows
                            If Not IsDBNull(r(col)) Then
                                Dim v As Double = Convert.ToDouble(r(col))
                                If v > maxVal Then maxVal = v
                            End If
                        Next
                    End If
                Next
                If maxVal = Double.MinValue Then Return 0
                Return CInt(Math.Round(maxVal))
            End Using
        Catch ex As Exception
            Return 0
        End Try
    End Function

    ' データ構造定義
    Public Class ModelData
        <LoadColumn(0)> Public モデル連番 As Single
        <LoadColumn(1)> Public 出荷先数 As Single
        <LoadColumn(2)> Public アイテム数 As Single
        <LoadColumn(3)> Public バラ数量 As Single
        <LoadColumn(4)> Public 行数 As Single
        <LoadColumn(5)> Public E1 As Single
        <LoadColumn(6)> Public E2 As Single
        <LoadColumn(7)> Public E3 As Single
        <LoadColumn(8)> Public E4 As Single
        <LoadColumn(9)> Public E5 As Single
        <LoadColumn(10)> Public I1 As Single
        <LoadColumn(11)> Public I2 As Single
        <LoadColumn(12)> Public I3 As Single
        <LoadColumn(13)> Public I4 As Single
        <LoadColumn(14)> Public I5 As Single
        ' 行数01-25, バラ数01-25, ケース01-25, PL01-25, 容積01-25, 重量01-25も同様に定義
        ' (省略：必要に応じて追加)
    End Class

    ' 入力データ構造
    Public Class InputData
        Public 出荷先数 As Single
        Public アイテム数 As Single
    End Class

    ' 単一予測結果クラス
    Public Class SinglePrediction
        <ColumnName("Score")>
        Public Score As Single
    End Class
#End Region

#Region "画面操作"
    ' ========================================
    ' フォームロード
    ' ========================================
    Private Sub Tera計算EIQAI_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ファイル確認()
        MyDB = Dat保存Path & "\EIQ_AIモデル.accdb"
        Dim Saizu As Integer = 300 ' MB
        Access最適化(MyDB, Saizu)

        Try
            ' MLContextを初期化
            mlContext = New MLContext(seed:=0)
            modelFolder = モデル保存Path
            trainedModels = New Dictionary(Of String, ITransformer)

            ' モデルフォルダの確認
            If Not Directory.Exists(modelFolder) Then
                MsgBox("学習済モデルフォルダが見つかりません。" & vbCrLf &
                      $"フォルダ: {modelFolder}" & vbCrLf & vbCrLf &
                      "モデルファイル(.zip)を上記フォルダに配置してください。",
                      MsgBoxStyle.Exclamation, "フォルダ未検出")
            End If

            出力フィールド名リスト初期化()   ' 出力フィールド名リストを初期化
            学習済モデル読込()               ' 自動的にモデルを読み込む
            AI予測EIQマトリクス表Dgv初期化() ' AI予測EIQマトリクス表Dgvのヘッダー初期化

            Debug.WriteLine("modelFolder=" & modelFolder)
            Debug.WriteLine("MyDB=" & MyDB)

        Catch ex As Exception
            MsgBox($"初期化エラー: {ex.Message}", MsgBoxStyle.Critical, "エラー")
        End Try
    End Sub
    ' ========================================
    ' 学習済モデル読込ボタン
    ' ========================================
    Private Sub 学習済モデル読込Bt_Click(sender As Object, e As EventArgs) Handles 学習済モデル読込Bt.Click
        学習済モデル読込()
        If trainedModels.Count > 0 Then
            MsgBox($"{trainedModels.Count} 個のモデルを読み込みました。",
                  MsgBoxStyle.Information, "読込完了")
        End If
    End Sub
    ' ========================================
    ' 予測実行ボタン
    ' ========================================
    Private Sub 予測実行Bt_Click(sender As Object, e As EventArgs) Handles 予測実行Bt.Click
        出荷先数2Tb.Text = 出荷先数Tb.Text
        アイテム数2Tb.Text = アイテム数Tb.Text
        予測実行()
    End Sub
    Private Sub EIQマトリクス表実行Bt_Click(sender As Object, e As EventArgs) Handles EIQマトリクス表実行Bt.Click
        AI予測EIQマトリクス表()
        AI予測従来計算E（)
        AI予測従来計算I（)
        matrixActive = True
    End Sub

    ' 表示単位のラジオボタンが変更されたら、マトリクスが有効な場合に再描画する
    Private Sub 抽出_表示単位_CheckedChanged(sender As Object, e As EventArgs) Handles 抽出_出庫回数Rb.CheckedChanged, 抽出_バラRb.CheckedChanged, 抽出_ケースRb.CheckedChanged, 抽出_PLRb.CheckedChanged, 抽出_容積Rb.CheckedChanged, 抽出_重量Rb.CheckedChanged
        If matrixActive Then
            AI予測EIQマトリクス表()
        End If
    End Sub
    Private Sub AI予測と実計算比較移動Bt_Click(sender As Object, e As EventArgs) Handles AI予測と実計算比較移動Bt.Click
        Tera計算EIQAI_Tc.SelectedTab = AI予測と実計算比較Tp
    End Sub
    Private Sub 比較開始Bt_Click(sender As Object, e As EventArgs) Handles 比較開始Bt.Click
        比較開始()
    End Sub
    Private Sub 比較開始()

        ' 1) 元の出荷先数・アイテム数を取得

        Dim 元出荷先数 As Integer = GetMaxRankFromTable("T160_全出荷E集計")
        Dim 元アテム数 As Integer = GetMaxRankFromTable("T160_全出荷I集計")

        If 元出荷先数 = 0 OrElse 元アテム数 = 0 Then
            MsgBox("T160 テーブルから元サイズを取得できませんでした。DBにテーブルが存在するか確認してください。", MsgBoxStyle.Exclamation, "取得エラー")
            Return
        End If

        ' 2) 指定比率を計算
        Dim 指定出荷先数 As Double
        Dim 指定アイテム数 As Double
        If Not Double.TryParse(出荷先数2Tb.Text, 指定出荷先数) Then
            MsgBox("出荷先数(2) が数値ではありません。", MsgBoxStyle.Exclamation, "入力エラー")
            Return
        End If
        If Not Double.TryParse(アイテム数2Tb.Text, 指定アイテム数) Then
            MsgBox("アイテム数(2) が数値ではありません。", MsgBoxStyle.Exclamation, "入力エラー")
            Return
        End If

        Dim 出荷先比率 As Double = 指定出荷先数 / 元出荷先数
        Dim アイテム比率 As Double = 指定アイテム数 / 元アテム数


        ' 予測計算1Gb の内容を予測計算2Gb へ複写
        '予測計算Gb作成()
        ' T710_E判定 / T710_I判定 を作成（T410 のルールに準拠）
        Try
            T710テーブル作成("T710_E判定", 出荷先比率, 12345)
            T710テーブル作成("T710_I判定", アイテム比率, 22345)
            ' T800_手計算対象 を作成（T500 と同様のルール）
            T800_手計算対象作成(出荷先比率, アイテム比率)
            ' T800 から T900_データモデル（単一行）を作成
            T900_データモデル作成()
            ' 実計算表示を更新 (T900 を UI に反映)
            Update実計算表示FromT900()
            MsgBox("T710_E判定 と T710_I判定、T800_手計算対象、および T900_データモデル を作成しました。", MsgBoxStyle.Information, "完了")
        Catch ex As Exception
            MsgBox($"T710/T800 作成エラー: {ex.Message}", MsgBoxStyle.Critical, "エラー")
        End Try
    End Sub

    Private Sub T900_データモデル作成()
        If String.IsNullOrEmpty(MyDB) OrElse Not File.Exists(MyDB) Then
            Throw New Exception("DBファイルが見つかりません")
        End If

        Using conn As New OleDbConnection($"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={MyDB}")
            conn.Open()

            Try
                ' === E1-E5 (出荷先数 by GPLEランク) ===
                Try
                    Using cmdDrop As New OleDbCommand("DROP TABLE T590_E出荷先一時", conn)
                        cmdDrop.ExecuteNonQuery()
                    End Using
                Catch
                End Try

                Using cmdCreateTempE As New OleDbCommand("SELECT GPLEランク, 出荷先 INTO T590_E出荷先一時 FROM T800_手計算対象 GROUP BY GPLEランク, 出荷先", conn)
                    Try
                        cmdCreateTempE.ExecuteNonQuery()
                    Catch
                    End Try
                End Using

                Dim E1 As Double = 0, E2 As Double = 0, E3 As Double = 0, E4 As Double = 0, E5 As Double = 0
                Using cmdE As New OleDbCommand("SELECT GPLEランク, Count(出荷先) AS 出荷先数 FROM T590_E出荷先一時 GROUP BY GPLEランク", conn)
                    Using readerE As OleDbDataReader = cmdE.ExecuteReader()
                        While readerE.Read()
                            If Not IsDBNull(readerE("GPLEランク")) AndAlso Not IsDBNull(readerE("出荷先数")) Then
                                Dim ランク As String = readerE("GPLEランク").ToString()
                                Dim 数 As Double = Convert.ToDouble(readerE("出荷先数"))
                                Select Case ランク
                                    Case "GPLE_A1" : E1 = 数
                                    Case "GPLE_A2" : E2 = 数
                                    Case "GPLE_B" : E3 = 数
                                    Case "GPLE_C" : E4 = 数
                                    Case "GPLE_D" : E5 = 数
                                End Select
                            End If
                        End While
                    End Using
                End Using

                ' drop temp
                Try
                    Using cmdDrop As New OleDbCommand("DROP TABLE T590_E出荷先一時", conn)
                        cmdDrop.ExecuteNonQuery()
                    End Using
                Catch
                End Try

                ' === I1-I5 (アイテム数 by GPLIランク) ===
                Try
                    Using cmdDropI As New OleDbCommand("DROP TABLE T590_Iアイテム一時", conn)
                        cmdDropI.ExecuteNonQuery()
                    End Using
                Catch
                End Try

                Using cmdCreateTempI As New OleDbCommand("SELECT GPLIランク, アイテム INTO T590_Iアイテム一時 FROM T800_手計算対象 GROUP BY GPLIランク, アイテム", conn)
                    Try
                        cmdCreateTempI.ExecuteNonQuery()
                    Catch
                    End Try
                End Using

                Dim I1 As Double = 0, I2 As Double = 0, I3 As Double = 0, I4 As Double = 0, I5 As Double = 0
                Using cmdI As New OleDbCommand("SELECT GPLIランク, Count(アイテム) AS アイテム数 FROM T590_Iアイテム一時 GROUP BY GPLIランク", conn)
                    Using readerI As OleDbDataReader = cmdI.ExecuteReader()
                        While readerI.Read()
                            If Not IsDBNull(readerI("GPLIランク")) AndAlso Not IsDBNull(readerI("アイテム数")) Then
                                Dim ランク As String = readerI("GPLIランク").ToString()
                                Dim 数 As Double = Convert.ToDouble(readerI("アイテム数"))
                                Select Case ランク
                                    Case "GPLI_A1" : I1 = 数
                                    Case "GPLI_A2" : I2 = 数
                                    Case "GPLI_B" : I3 = 数
                                    Case "GPLI_C" : I4 = 数
                                    Case "GPLI_D" : I5 = 数
                                End Select
                            End If
                        End While
                    End Using
                End Using

                Try
                    Using cmdDropI As New OleDbCommand("DROP TABLE T590_Iアイテム一時", conn)
                        cmdDropI.ExecuteNonQuery()
                    End Using
                Catch
                End Try

                ' === バラ数量 ===
                Dim バラ数量 As Double = 0
                Using cmdBara As New OleDbCommand("SELECT SUM(バラ数) FROM T800_手計算対象", conn)
                    Dim result = cmdBara.ExecuteScalar()
                    If Not IsDBNull(result) AndAlso result IsNot Nothing Then
                        バラ数量 = Convert.ToDouble(result)
                    End If
                End Using

                ' === 行数 ===
                Dim 行数 As Double = 0
                Using cmdCount As New OleDbCommand("SELECT COUNT(*) FROM T800_手計算対象", conn)
                    Dim result = cmdCount.ExecuteScalar()
                    If result IsNot Nothing AndAlso Not IsDBNull(result) Then
                        Double.TryParse(result.ToString(), 行数)
                    End If
                End Using

                ' === 行数01-25 ===
                Dim 行数Arr(25) As Double
                Using cmdRows As New OleDbCommand("SELECT GPLEランク, GPLIランク, COUNT(*) AS 行数 FROM T800_手計算対象 GROUP BY GPLEランク, GPLIランク", conn)
                    Using reader As OleDbDataReader = cmdRows.ExecuteReader()
                        While reader.Read()
                            If Not IsDBNull(reader("GPLEランク")) AndAlso Not IsDBNull(reader("GPLIランク")) AndAlso Not IsDBNull(reader("行数")) Then
                                Dim GPLEランク As String = reader("GPLEランク").ToString()
                                Dim GPLIランク As String = reader("GPLIランク").ToString()
                                Dim 数 As Double = Convert.ToDouble(reader("行数"))
                                Dim キー As String = GPLEランク & "_" & GPLIランク
                                Select Case キー
                                    Case "GPLE_A1_GPLI_A1" : 行数Arr(1) = 数
                                    Case "GPLE_A1_GPLI_A2" : 行数Arr(2) = 数
                                    Case "GPLE_A1_GPLI_B" : 行数Arr(3) = 数
                                    Case "GPLE_A1_GPLI_C" : 行数Arr(4) = 数
                                    Case "GPLE_A1_GPLI_D" : 行数Arr(5) = 数
                                    Case "GPLE_A2_GPLI_A1" : 行数Arr(6) = 数
                                    Case "GPLE_A2_GPLI_A2" : 行数Arr(7) = 数
                                    Case "GPLE_A2_GPLI_B" : 行数Arr(8) = 数
                                    Case "GPLE_A2_GPLI_C" : 行数Arr(9) = 数
                                    Case "GPLE_A2_GPLI_D" : 行数Arr(10) = 数
                                    Case "GPLE_B_GPLI_A1" : 行数Arr(11) = 数
                                    Case "GPLE_B_GPLI_A2" : 行数Arr(12) = 数
                                    Case "GPLE_B_GPLI_B" : 行数Arr(13) = 数
                                    Case "GPLE_B_GPLI_C" : 行数Arr(14) = 数
                                    Case "GPLE_B_GPLI_D" : 行数Arr(15) = 数
                                    Case "GPLE_C_GPLI_A1" : 行数Arr(16) = 数
                                    Case "GPLE_C_GPLI_A2" : 行数Arr(17) = 数
                                    Case "GPLE_C_GPLI_B" : 行数Arr(18) = 数
                                    Case "GPLE_C_GPLI_C" : 行数Arr(19) = 数
                                    Case "GPLE_C_GPLI_D" : 行数Arr(20) = 数
                                    Case "GPLE_D_GPLI_A1" : 行数Arr(21) = 数
                                    Case "GPLE_D_GPLI_A2" : 行数Arr(22) = 数
                                    Case "GPLE_D_GPLI_B" : 行数Arr(23) = 数
                                    Case "GPLE_D_GPLI_C" : 行数Arr(24) = 数
                                    Case "GPLE_D_GPLI_D" : 行数Arr(25) = 数
                                End Select
                            End If
                        End While
                    End Using
                End Using

                ' === バラ数01-25 (Gバ sum) ===
                Dim バラArr(25) As Double
                Using cmdB As New OleDbCommand("SELECT GPLEランク, GPLIランク, Sum(Gバ) AS Gバの合計 FROM T800_手計算対象 GROUP BY GPLEランク, GPLIランク", conn)
                    Using readerB As OleDbDataReader = cmdB.ExecuteReader()
                        While readerB.Read()
                            If Not IsDBNull(readerB("GPLEランク")) AndAlso Not IsDBNull(readerB("GPLIランク")) AndAlso Not IsDBNull(readerB("Gバの合計")) Then
                                Dim GPLEランク As String = readerB("GPLEランク").ToString()
                                Dim GPLIランク As String = readerB("GPLIランク").ToString()
                                Dim 数 As Double = Convert.ToDouble(readerB("Gバの合計"))
                                Dim キー As String = GPLEランク & "_" & GPLIランク
                                Select Case キー
                                    Case "GPLE_A1_GPLI_A1" : バラArr(1) = 数
                                    Case "GPLE_A1_GPLI_A2" : バラArr(2) = 数
                                    Case "GPLE_A1_GPLI_B" : バラArr(3) = 数
                                    Case "GPLE_A1_GPLI_C" : バラArr(4) = 数
                                    Case "GPLE_A1_GPLI_D" : バラArr(5) = 数
                                    Case "GPLE_A2_GPLI_A1" : バラArr(6) = 数
                                    Case "GPLE_A2_GPLI_A2" : バラArr(7) = 数
                                    Case "GPLE_A2_GPLI_B" : バラArr(8) = 数
                                    Case "GPLE_A2_GPLI_C" : バラArr(9) = 数
                                    Case "GPLE_A2_GPLI_D" : バラArr(10) = 数
                                    Case "GPLE_B_GPLI_A1" : バラArr(11) = 数
                                    Case "GPLE_B_GPLI_A2" : バラArr(12) = 数
                                    Case "GPLE_B_GPLI_B" : バラArr(13) = 数
                                    Case "GPLE_B_GPLI_C" : バラArr(14) = 数
                                    Case "GPLE_B_GPLI_D" : バラArr(15) = 数
                                    Case "GPLE_C_GPLI_A1" : バラArr(16) = 数
                                    Case "GPLE_C_GPLI_A2" : バラArr(17) = 数
                                    Case "GPLE_C_GPLI_B" : バラArr(18) = 数
                                    Case "GPLE_C_GPLI_C" : バラArr(19) = 数
                                    Case "GPLE_C_GPLI_D" : バラArr(20) = 数
                                    Case "GPLE_D_GPLI_A1" : バラArr(21) = 数
                                    Case "GPLE_D_GPLI_A2" : バラArr(22) = 数
                                    Case "GPLE_D_GPLI_B" : バラArr(23) = 数
                                    Case "GPLE_D_GPLI_C" : バラArr(24) = 数
                                    Case "GPLE_D_GPLI_D" : バラArr(25) = 数
                                End Select
                            End If
                        End While
                    End Using
                End Using

                ' === ケース (Gケ) ===
                Dim ケースArr(25) As Double
                Using cmdK As New OleDbCommand("SELECT GPLEランク, GPLIランク, Sum(Gケ) AS Gケの合計 FROM T800_手計算対象 GROUP BY GPLEランク, GPLIランク", conn)
                    Using readerK As OleDbDataReader = cmdK.ExecuteReader()
                        While readerK.Read()
                            If Not IsDBNull(readerK("GPLEランク")) AndAlso Not IsDBNull(readerK("GPLIランク")) AndAlso Not IsDBNull(readerK("Gケの合計")) Then
                                Dim GPLEランク As String = readerK("GPLEランク").ToString()
                                Dim GPLIランク As String = readerK("GPLIランク").ToString()
                                Dim 数 As Double = Convert.ToDouble(readerK("Gケの合計"))
                                Dim キー As String = GPLEランク & "_" & GPLIランク
                                Select Case キー
                                    Case "GPLE_A1_GPLI_A1" : ケースArr(1) = 数
                                    Case "GPLE_A1_GPLI_A2" : ケースArr(2) = 数
                                    Case "GPLE_A1_GPLI_B" : ケースArr(3) = 数
                                    Case "GPLE_A1_GPLI_C" : ケースArr(4) = 数
                                    Case "GPLE_A1_GPLI_D" : ケースArr(5) = 数
                                    Case "GPLE_A2_GPLI_A1" : ケースArr(6) = 数
                                    Case "GPLE_A2_GPLI_A2" : ケースArr(7) = 数
                                    Case "GPLE_A2_GPLI_B" : ケースArr(8) = 数
                                    Case "GPLE_A2_GPLI_C" : ケースArr(9) = 数
                                    Case "GPLE_A2_GPLI_D" : ケースArr(10) = 数
                                    Case "GPLE_B_GPLI_A1" : ケースArr(11) = 数
                                    Case "GPLE_B_GPLI_A2" : ケースArr(12) = 数
                                    Case "GPLE_B_GPLI_B" : ケースArr(13) = 数
                                    Case "GPLE_B_GPLI_C" : ケースArr(14) = 数
                                    Case "GPLE_B_GPLI_D" : ケースArr(15) = 数
                                    Case "GPLE_C_GPLI_A1" : ケースArr(16) = 数
                                    Case "GPLE_C_GPLI_A2" : ケースArr(17) = 数
                                    Case "GPLE_C_GPLI_B" : ケースArr(18) = 数
                                    Case "GPLE_C_GPLI_C" : ケースArr(19) = 数
                                    Case "GPLE_C_GPLI_D" : ケースArr(20) = 数
                                    Case "GPLE_D_GPLI_A1" : ケースArr(21) = 数
                                    Case "GPLE_D_GPLI_A2" : ケースArr(22) = 数
                                    Case "GPLE_D_GPLI_B" : ケースArr(23) = 数
                                    Case "GPLE_D_GPLI_C" : ケースArr(24) = 数
                                    Case "GPLE_D_GPLI_D" : ケースArr(25) = 数
                                End Select
                            End If
                        End While
                    End Using
                End Using

                ' === PL (GPL) ===
                Dim PLArr(25) As Double
                Using cmdPL As New OleDbCommand("SELECT GPLEランク, GPLIランク, Sum(GPL) AS GPLの合計 FROM T800_手計算対象 GROUP BY GPLEランク, GPLIランク", conn)
                    Using readerPL As OleDbDataReader = cmdPL.ExecuteReader()
                        While readerPL.Read()
                            If Not IsDBNull(readerPL("GPLEランク")) AndAlso Not IsDBNull(readerPL("GPLIランク")) AndAlso Not IsDBNull(readerPL("GPLの合計")) Then
                                Dim GPLEランク As String = readerPL("GPLEランク").ToString()
                                Dim GPLIランク As String = readerPL("GPLIランク").ToString()
                                Dim 数 As Double = Convert.ToDouble(readerPL("GPLの合計"))
                                Dim キー As String = GPLEランク & "_" & GPLIランク
                                Select Case キー
                                    Case "GPLE_A1_GPLI_A1" : PLArr(1) = 数
                                    Case "GPLE_A1_GPLI_A2" : PLArr(2) = 数
                                    Case "GPLE_A1_GPLI_B" : PLArr(3) = 数
                                    Case "GPLE_A1_GPLI_C" : PLArr(4) = 数
                                    Case "GPLE_A1_GPLI_D" : PLArr(5) = 数
                                    Case "GPLE_A2_GPLI_A1" : PLArr(6) = 数
                                    Case "GPLE_A2_GPLI_A2" : PLArr(7) = 数
                                    Case "GPLE_A2_GPLI_B" : PLArr(8) = 数
                                    Case "GPLE_A2_GPLI_C" : PLArr(9) = 数
                                    Case "GPLE_A2_GPLI_D" : PLArr(10) = 数
                                    Case "GPLE_B_GPLI_A1" : PLArr(11) = 数
                                    Case "GPLE_B_GPLI_A2" : PLArr(12) = 数
                                    Case "GPLE_B_GPLI_B" : PLArr(13) = 数
                                    Case "GPLE_B_GPLI_C" : PLArr(14) = 数
                                    Case "GPLE_B_GPLI_D" : PLArr(15) = 数
                                    Case "GPLE_C_GPLI_A1" : PLArr(16) = 数
                                    Case "GPLE_C_GPLI_A2" : PLArr(17) = 数
                                    Case "GPLE_C_GPLI_B" : PLArr(18) = 数
                                    Case "GPLE_C_GPLI_C" : PLArr(19) = 数
                                    Case "GPLE_C_GPLI_D" : PLArr(20) = 数
                                    Case "GPLE_D_GPLI_A1" : PLArr(21) = 数
                                    Case "GPLE_D_GPLI_A2" : PLArr(22) = 数
                                    Case "GPLE_D_GPLI_B" : PLArr(23) = 数
                                    Case "GPLE_D_GPLI_C" : PLArr(24) = 数
                                    Case "GPLE_D_GPLI_D" : PLArr(25) = 数
                                End Select
                            End If
                        End While
                    End Using
                End Using

                ' === 容積 (G容) ===
                Dim 容積Arr(25) As Double
                Using cmd容積 As New OleDbCommand("SELECT GPLEランク, GPLIランク, Sum(G容) AS G容の合計 FROM T800_手計算対象 GROUP BY GPLEランク, GPLIランク", conn)
                    Using reader容積 As OleDbDataReader = cmd容積.ExecuteReader()
                        While reader容積.Read()
                            If Not IsDBNull(reader容積("GPLEランク")) AndAlso Not IsDBNull(reader容積("GPLIランク")) AndAlso Not IsDBNull(reader容積("G容の合計")) Then
                                Dim GPLEランク As String = reader容積("GPLEランク").ToString()
                                Dim GPLIランク As String = reader容積("GPLIランク").ToString()
                                Dim 数 As Double = Convert.ToDouble(reader容積("G容の合計"))
                                Dim キー As String = GPLEランク & "_" & GPLIランク
                                Select Case キー
                                    Case "GPLE_A1_GPLI_A1" : 容積Arr(1) = 数
                                    Case "GPLE_A1_GPLI_A2" : 容積Arr(2) = 数
                                    Case "GPLE_A1_GPLI_B" : 容積Arr(3) = 数
                                    Case "GPLE_A1_GPLI_C" : 容積Arr(4) = 数
                                    Case "GPLE_A1_GPLI_D" : 容積Arr(5) = 数
                                    Case "GPLE_A2_GPLI_A1" : 容積Arr(6) = 数
                                    Case "GPLE_A2_GPLI_A2" : 容積Arr(7) = 数
                                    Case "GPLE_A2_GPLI_B" : 容積Arr(8) = 数
                                    Case "GPLE_A2_GPLI_C" : 容積Arr(9) = 数
                                    Case "GPLE_A2_GPLI_D" : 容積Arr(10) = 数
                                    Case "GPLE_B_GPLI_A1" : 容積Arr(11) = 数
                                    Case "GPLE_B_GPLI_A2" : 容積Arr(12) = 数
                                    Case "GPLE_B_GPLI_B" : 容積Arr(13) = 数
                                    Case "GPLE_B_GPLI_C" : 容積Arr(14) = 数
                                    Case "GPLE_B_GPLI_D" : 容積Arr(15) = 数
                                    Case "GPLE_C_GPLI_A1" : 容積Arr(16) = 数
                                    Case "GPLE_C_GPLI_A2" : 容積Arr(17) = 数
                                    Case "GPLE_C_GPLI_B" : 容積Arr(18) = 数
                                    Case "GPLE_C_GPLI_C" : 容積Arr(19) = 数
                                    Case "GPLE_C_GPLI_D" : 容積Arr(20) = 数
                                    Case "GPLE_D_GPLI_A1" : 容積Arr(21) = 数
                                    Case "GPLE_D_GPLI_A2" : 容積Arr(22) = 数
                                    Case "GPLE_D_GPLI_B" : 容積Arr(23) = 数
                                    Case "GPLE_D_GPLI_C" : 容積Arr(24) = 数
                                    Case "GPLE_D_GPLI_D" : 容積Arr(25) = 数
                                End Select
                            End If
                        End While
                    End Using
                End Using

                ' === 重量 (G重) ===
                Dim 重量Arr(25) As Double
                Using cmd重 As New OleDbCommand("SELECT GPLEランク, GPLIランク, Sum(G重) AS G重の合計 FROM T800_手計算対象 GROUP BY GPLEランク, GPLIランク", conn)
                    Using reader重 As OleDbDataReader = cmd重.ExecuteReader()
                        While reader重.Read()
                            If Not IsDBNull(reader重("GPLEランク")) AndAlso Not IsDBNull(reader重("GPLIランク")) AndAlso Not IsDBNull(reader重("G重の合計")) Then
                                Dim GPLEランク As String = reader重("GPLEランク").ToString()
                                Dim GPLIランク As String = reader重("GPLIランク").ToString()
                                Dim 数 As Double = Convert.ToDouble(reader重("G重の合計"))
                                Dim キー As String = GPLEランク & "_" & GPLIランク
                                Select Case キー
                                    Case "GPLE_A1_GPLI_A1" : 重量Arr(1) = 数
                                    Case "GPLE_A1_GPLI_A2" : 重量Arr(2) = 数
                                    Case "GPLE_A1_GPLI_B" : 重量Arr(3) = 数
                                    Case "GPLE_A1_GPLI_C" : 重量Arr(4) = 数
                                    Case "GPLE_A1_GPLI_D" : 重量Arr(5) = 数
                                    Case "GPLE_A2_GPLI_A1" : 重量Arr(6) = 数
                                    Case "GPLE_A2_GPLI_A2" : 重量Arr(7) = 数
                                    Case "GPLE_A2_GPLI_B" : 重量Arr(8) = 数
                                    Case "GPLE_A2_GPLI_C" : 重量Arr(9) = 数
                                    Case "GPLE_A2_GPLI_D" : 重量Arr(10) = 数
                                    Case "GPLE_B_GPLI_A1" : 重量Arr(11) = 数
                                    Case "GPLE_B_GPLI_A2" : 重量Arr(12) = 数
                                    Case "GPLE_B_GPLI_B" : 重量Arr(13) = 数
                                    Case "GPLE_B_GPLI_C" : 重量Arr(14) = 数
                                    Case "GPLE_B_GPLI_D" : 重量Arr(15) = 数
                                    Case "GPLE_C_GPLI_A1" : 重量Arr(16) = 数
                                    Case "GPLE_C_GPLI_A2" : 重量Arr(17) = 数
                                    Case "GPLE_C_GPLI_B" : 重量Arr(18) = 数
                                    Case "GPLE_C_GPLI_C" : 重量Arr(19) = 数
                                    Case "GPLE_C_GPLI_D" : 重量Arr(20) = 数
                                    Case "GPLE_D_GPLI_A1" : 重量Arr(21) = 数
                                    Case "GPLE_D_GPLI_A2" : 重量Arr(22) = 数
                                    Case "GPLE_D_GPLI_B" : 重量Arr(23) = 数
                                    Case "GPLE_D_GPLI_C" : 重量Arr(24) = 数
                                    Case "GPLE_D_GPLI_D" : 重量Arr(25) = 数
                                End Select
                            End If
                        End While
                    End Using
                End Using

                ' === T900 テーブル作成（既存を削除して単一行を作成） ===
                Try
                    Using cmdDrop As New OleDbCommand("DROP TABLE T900_データモデル", conn)
                        cmdDrop.ExecuteNonQuery()
                    End Using
                Catch
                End Try

                ' Build CREATE TABLE statement
                Dim cols As New List(Of String)()
                cols.Add("モデル連番 LONG")
                cols.Add("出荷先数 DOUBLE")
                cols.Add("アイテム数 DOUBLE")
                cols.Add("バラ数量 DOUBLE")
                cols.Add("行数 DOUBLE")
                For i As Integer = 1 To 5
                    cols.Add($"E{i} DOUBLE")
                Next
                For i As Integer = 1 To 5
                    cols.Add($"I{i} DOUBLE")
                Next
                For i As Integer = 1 To 25
                    cols.Add($"行数{i:D2} DOUBLE")
                Next
                For i As Integer = 1 To 25
                    cols.Add($"バラ数{i:D2} DOUBLE")
                Next
                For i As Integer = 1 To 25
                    cols.Add($"ケース{i:D2} DOUBLE")
                Next
                For i As Integer = 1 To 25
                    cols.Add($"PL{i:D2} DOUBLE")
                Next
                For i As Integer = 1 To 25
                    cols.Add($"容積{i:D2} DOUBLE")
                Next
                For i As Integer = 1 To 25
                    cols.Add($"重量{i:D2} DOUBLE")
                Next

                Dim createSql As String = $"CREATE TABLE T900_データモデル (" & String.Join(",", cols) & ")"
                Using cmdCreate As New OleDbCommand(createSql, conn)
                    cmdCreate.ExecuteNonQuery()
                End Using

                ' Insert single row (モデル連番 = 1)
                Dim colNames As New List(Of String)()
                Dim paramNames As New List(Of String)()
                Dim paramValues As New List(Of Object)()

                colNames.Add("モデル連番") : paramNames.Add("@p0") : paramValues.Add(1)
                colNames.Add("出荷先数") : paramNames.Add("@p1") : paramValues.Add(E1 + E2 + E3 + E4 + E5)
                colNames.Add("アイテム数") : paramNames.Add("@p2") : paramValues.Add(I1 + I2 + I3 + I4 + I5)
                colNames.Add("バラ数量") : paramNames.Add("@p3") : paramValues.Add(バラ数量)
                colNames.Add("行数") : paramNames.Add("@p4") : paramValues.Add(行数)

                ' E1..E5
                For i As Integer = 1 To 5
                    colNames.Add($"E{i}") : paramNames.Add($"@p{colNames.Count}")
                Next
                ' I1..I5
                For i As Integer = 1 To 5
                    colNames.Add($"I{i}") : paramNames.Add($"@p{colNames.Count}")
                Next
                ' 行数01-25
                For i As Integer = 1 To 25
                    colNames.Add($"行数{i:D2}") : paramNames.Add($"@p{colNames.Count}")
                Next
                ' バラ数01-25
                For i As Integer = 1 To 25
                    colNames.Add($"バラ数{i:D2}") : paramNames.Add($"@p{colNames.Count}")
                Next
                ' ケース01-25
                For i As Integer = 1 To 25
                    colNames.Add($"ケース{i:D2}") : paramNames.Add($"@p{colNames.Count}")
                Next
                ' PL01-25
                For i As Integer = 1 To 25
                    colNames.Add($"PL{i:D2}") : paramNames.Add($"@p{colNames.Count}")
                Next
                ' 容積01-25
                For i As Integer = 1 To 25
                    colNames.Add($"容積{i:D2}") : paramNames.Add($"@p{colNames.Count}")
                Next
                ' 重量01-25
                For i As Integer = 1 To 25
                    colNames.Add($"重量{i:D2}") : paramNames.Add($"@p{colNames.Count}")
                Next

                ' Build values list for parameters
                Dim values As New List(Of Object)()
                ' E and I values first: fill E1..E5, I1..I5
                values.Add(E1) : values.Add(E2) : values.Add(E3) : values.Add(E4) : values.Add(E5)
                values.Add(I1) : values.Add(I2) : values.Add(I3) : values.Add(I4) : values.Add(I5)
                ' 行数01..25
                For i As Integer = 1 To 25
                    values.Add(行数Arr(i))
                Next
                ' バラ数01..25
                For i As Integer = 1 To 25
                    values.Add(バラArr(i))
                Next
                ' ケース
                For i As Integer = 1 To 25
                    values.Add(ケースArr(i))
                Next
                ' PL
                For i As Integer = 1 To 25
                    values.Add(PLArr(i))
                Next
                ' 容積
                For i As Integer = 1 To 25
                    values.Add(容積Arr(i))
                Next
                ' 重量
                For i As Integer = 1 To 25
                    values.Add(重量Arr(i))
                Next

                ' Now assemble parameterized INSERT (note: paramValues list must align)
                ' We've already added model連番 and summary fields earlier, need to combine
                ' Build final parameter list in order of colNames
                Dim finalParams As New List(Of Object)()
                ' First two summary numbers were already added to paramValues earlier as p1..p4; to simplify, rebuild sequentially:
                finalParams.Add(1) ' モデル連番
                finalParams.Add(E1 + E2 + E3 + E4 + E5) ' 出荷先数
                finalParams.Add(I1 + I2 + I3 + I4 + I5) ' アイテム数
                finalParams.Add(バラ数量)
                finalParams.Add(行数)
                ' E1..E5
                finalParams.AddRange(New Object() {E1, E2, E3, E4, E5})
                ' I1..I5
                finalParams.AddRange(New Object() {I1, I2, I3, I4, I5})
                ' 行数01..25
                finalParams.AddRange(values.GetRange(0, 25))
                ' バラ数01..25
                finalParams.AddRange(values.GetRange(25, 25))
                ' ケース
                finalParams.AddRange(values.GetRange(50, 25))
                ' PL
                finalParams.AddRange(values.GetRange(75, 25))
                ' 容積
                finalParams.AddRange(values.GetRange(100, 25))
                ' 重量
                finalParams.AddRange(values.GetRange(125, 25))

                ' Build parameter placeholders
                Dim placeholders As New List(Of String)()
                For i As Integer = 0 To finalParams.Count - 1
                    placeholders.Add("@v" & i.ToString())
                Next

                Dim insertSql As String = $"INSERT INTO T900_データモデル (" & String.Join(",", colNames) & ") VALUES (" & String.Join(",", placeholders) & ")"
                Using cmdIns As New OleDbCommand(insertSql, conn)
                    For i As Integer = 0 To finalParams.Count - 1
                        cmdIns.Parameters.AddWithValue(placeholders(i), finalParams(i))
                    Next
                    cmdIns.ExecuteNonQuery()
                End Using

            Catch ex As Exception
                MsgBox($"T900_データモデル作成エラー: {ex.Message}", MsgBoxStyle.Critical, "エラー")
            End Try
        End Using
    End Sub

    Private Sub 結果エクスポートBt_Click(sender As Object, e As EventArgs) Handles 結果エクスポートBt.Click
        結果エクスポート()
    End Sub
    ' 表示チェック2Gbのラジオボタン変更時に再描画
    Private Sub 表示チェック2Gb_Changed(sender As Object, e As EventArgs) Handles 抽出_出庫回数2Rb.CheckedChanged, 抽出_バラ2Rb.CheckedChanged, 抽出_ケース2Rb.CheckedChanged, 抽出_PL2Rb.CheckedChanged, 抽出_容積2Rb.CheckedChanged, 抽出_重量2Rb.CheckedChanged
        予測計算Gb作成()
        ' 実計算表示も同期して更新（T900 があれば反映される）
        Try
            Update実計算表示FromT900()
        Catch
            ' 更新できなくても無視
        End Try
    End Sub
    Private Sub 閉じるBt_Click(sender As Object, e As EventArgs) Handles 閉じるBt.Click
        Me.Close()
    End Sub
#End Region


    ' ========================================
    ' 出力フィールド名リストの初期化
    ' ========================================
    Private Sub 出力フィールド名リスト初期化()
        出力フィールド名リスト = New List(Of String) From {
            "バラ数量", "行数",
            "E1", "E2", "E3", "E4", "E5",
            "I1", "I2", "I3", "I4", "I5"
        }

        ' 行数01-25を追加
        For i As Integer = 1 To 25
            出力フィールド名リスト.Add($"行数{i:D2}")
        Next

        ' バラ数01-25を追加
        For i As Integer = 1 To 25
            出力フィールド名リスト.Add($"バラ数{i:D2}")
        Next

        ' ケース01-25を追加
        For i As Integer = 1 To 25
            出力フィールド名リスト.Add($"ケース{i:D2}")
        Next

        ' PL01-25を追加
        For i As Integer = 1 To 25
            出力フィールド名リスト.Add($"PL{i:D2}")
        Next

        ' 容積01-25を追加
        For i As Integer = 1 To 25
            出力フィールド名リスト.Add($"容積{i:D2}")
        Next

        ' 重量01-25を追加
        For i As Integer = 1 To 25
            出力フィールド名リスト.Add($"重量{i:D2}")
        Next
    End Sub

    ' ========================================
    ' 学習済モデル読込
    ' ========================================
    Private Sub 学習済モデル読込()
        Try
            trainedModels.Clear()

            If Not Directory.Exists(modelFolder) Then
                モデル状態Lb.Text = "モデル: フォルダが見つかりません"
                モデル状態Lb.ForeColor = Color.Red
                Exit Sub
            End If

            ' モデルフォルダ内のすべての.zipファイルを読み込む
            Dim modelFiles = Directory.GetFiles(modelFolder, "*.zip")

            If modelFiles.Length = 0 Then
                モデル状態Lb.Text = "モデル: ファイルが見つかりません"
                モデル状態Lb.ForeColor = Color.Red
                Exit Sub
            End If

            For Each modelPath As String In modelFiles
                Try
                    Dim model = mlContext.Model.Load(modelPath, Nothing)
                    Dim fieldName As String = Path.GetFileNameWithoutExtension(modelPath)
                    trainedModels(fieldName) = model
                Catch ex As Exception
                    ' 読み込めないモデルはスキップ
                    Continue For
                End Try
            Next

            モデル状態Lb.Text = $"モデル: {trainedModels.Count} 個読込済"
            モデル状態Lb.ForeColor = Color.Green

        Catch ex As Exception
            モデル状態Lb.Text = "モデル: 読込エラー"
            モデル状態Lb.ForeColor = Color.Red
            MsgBox($"モデル読込エラー: {ex.Message}", MsgBoxStyle.Critical, "エラー")
        End Try
    End Sub

    Private Sub 予測実行()

        Try
            ' モデルが読み込まれているか確認
            If trainedModels Is Nothing OrElse trainedModels.Count = 0 Then
                MsgBox("モデルが読み込まれていません。" & vbCrLf &
                      "学習済モデルを配置してください。",
                      MsgBoxStyle.Exclamation, "モデル未読込")
                Exit Sub
            End If

            ' 入力値の検証
            Dim 出荷先数値 As Single
            Dim アイテム数値 As Single

            If Not Single.TryParse(出荷先数Tb.Text, 出荷先数値) Then
                MsgBox("出荷先数に有効な数値を入力してください。",
                      MsgBoxStyle.Exclamation, "入力エラー")
                出荷先数Tb.Focus()
                Exit Sub
            End If

            If Not Single.TryParse(アイテム数Tb.Text, アイテム数値) Then
                MsgBox("アイテム数に有効な数値を入力してください。",
                      MsgBoxStyle.Exclamation, "入力エラー")
                アイテム数Tb.Focus()
                Exit Sub
            End If

            ' 入力データを作成
            Dim input As New InputData With {
                .出荷先数 = 出荷先数値,
                .アイテム数 = アイテム数値
            }

            ' 予測状態表示
            予測状態Lb.Text = "予測: 実行中..."
            予測状態Lb.ForeColor = Color.Orange
            Application.DoEvents()

            ' 各モデルで予測を実行
            Dim predictions As New Dictionary(Of String, Single)

            For Each kvp As KeyValuePair(Of String, ITransformer) In trainedModels
                Try
                    Dim fieldName As String = kvp.Key
                    Dim model = kvp.Value

                    ' 予測エンジンを作成（単一出力用）
                    Dim predictionEngine = mlContext.Model.CreatePredictionEngine(Of InputData, SinglePrediction)(model)

                    ' 予測を実行
                    Dim prediction = predictionEngine.Predict(input)

                    ' 結果を保存
                    predictions(fieldName) = prediction.Score

                Catch ex As Exception
                    ' エラーがあればスキップ
                    Continue For
                End Try
            Next

            ' 結果を表示
            予測結果表示(predictions)

            予測状態Lb.Text = $"予測: 完了 ({predictions.Count} 項目)"
            予測状態Lb.ForeColor = Color.Green

            ' MsgBox($"予測が完了しました。{vbCrLf}{predictions.Count} 項目の予測値を計算しました。", MsgBoxStyle.Information, "予測完了")

        Catch ex As Exception
            予測状態Lb.Text = "予測: エラー"
            予測状態Lb.ForeColor = Color.Red
            MsgBox($"予測エラー: {ex.Message}", MsgBoxStyle.Critical, "エラー")
        End Try
    End Sub

    ' ========================================
    ' 予測結果を表示
    ' ========================================
    Private Sub 予測結果表示(predictions As Dictionary(Of String, Single))
        ' DataGridViewに結果を表示
        Dim dt As New DataTable()
        dt.Columns.Add("項目", GetType(String))
        dt.Columns.Add("予測値", GetType(String))

        ' 出力フィールド名リストの順序に従って予測結果を表示
        For Each fieldName As String In 出力フィールド名リスト
            If predictions.ContainsKey(fieldName) Then
                dt.Rows.Add(fieldName, predictions(fieldName).ToString("F2"))
            End If
        Next

        ' リストにないフィールドも追加（念のため）
        For Each kvp As KeyValuePair(Of String, Single) In predictions
            If Not 出力フィールド名リスト.Contains(kvp.Key) Then
                dt.Rows.Add(kvp.Key, kvp.Value.ToString("F2"))
            End If
        Next

        予測結果Dgv.DataSource = dt
        '予測結果Dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells

        ' 行ヘッダーを非表示にする
        予測結果Dgv.RowHeadersVisible = False
    End Sub

    ' ========================================
    ' 結果をCSVにエクスポート
    ' ========================================
    Private Sub 結果エクスポート()
        Try
            If 予測結果Dgv.DataSource Is Nothing Then
                MsgBox("エクスポートする予測結果がありません。",
                      MsgBoxStyle.Exclamation, "データなし")
                Exit Sub
            End If

            ' ファイル名を生成（入力条件を含めない簡潔版）
            Dim fileName As String = $"予測結果_{DateTime.Now:yyyyMMdd_HHmmss}.csv"
            Dim exportPath As String = Path.Combine(計算結果Path, fileName)

            ' フォルダが存在しない場合は作成
            Dim exportFolder As String = Path.GetDirectoryName(exportPath)
            If Not Directory.Exists(exportFolder) Then
                Directory.CreateDirectory(exportFolder)
            End If

            ' DataGridViewの内容をCSVに出力（入力条件も含める）
            Dim dt = CType(予測結果Dgv.DataSource, DataTable)

            Using writer As New StreamWriter(exportPath, False, System.Text.Encoding.UTF8)
                ' 入力条件をヘッダーとして出力
                writer.WriteLine($"# 出荷先数,{出荷先数Tb.Text}")
                writer.WriteLine($"# アイテム数,{アイテム数Tb.Text}")
                writer.WriteLine("# ---,")

                ' ヘッダー行（DataGridViewの列名と同じ）
                writer.WriteLine("項目,予測値")

                ' データ行（DataGridViewの内容と同じ）
                For Each row As DataRow In dt.Rows
                    writer.WriteLine($"{row(0)},{row(1)}")
                Next
            End Using

            MsgBox($"予測結果をエクスポートしました。{vbCrLf}保存先: {exportPath}",
                  MsgBoxStyle.Information, "エクスポート完了")

        Catch ex As Exception
            MsgBox($"エクスポートエラー: {ex.Message}", MsgBoxStyle.Critical, "エラー")
        End Try
    End Sub



＃Region "AI予測EIQマトリクス表作成"
    Private Sub AI予測EIQマトリクス表（)
        Dim nIndex As Integer

        If 抽出_出庫回数Rb.Checked = True Then nIndex = 13
        If 抽出_バラRb.Checked = True Then nIndex = 38
        If 抽出_ケースRb.Checked = True Then nIndex = 63
        If 抽出_PLRb.Checked = True Then nIndex = 88
        If 抽出_容積Rb.Checked = True Then nIndex = 113
        If 抽出_重量Rb.Checked = True Then nIndex = 138

        Try
            ' 予測結果が存在するか確認
            If 予測結果Dgv Is Nothing OrElse 予測結果Dgv.Rows Is Nothing OrElse 予測結果Dgv.Rows.Count = 0 Then
                ' クリア
                抽出I_A1Tb.Text = String.Empty
                抽出I_A2Tb.Text = String.Empty
                抽出I_BTb.Text = String.Empty
                抽出I_CTb.Text = String.Empty
                抽出I_DTb.Text = String.Empty
                抽出I_計Tb.Text = String.Empty
                Return
            End If

            ' 予測結果を辞書にキャッシュ（キー: 項目名, 値: 四捨五入した整数文字列）
            Dim pred As New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
            For Each r As DataGridViewRow In 予測結果Dgv.Rows
                If r.IsNewRow Then Continue For
                Dim key = If(r.Cells(0).Value, String.Empty).ToString()
                Dim raw = If(r.Cells(1).Value, String.Empty).ToString()
                If String.IsNullOrEmpty(key) Then Continue For
                pred(key) = ToRoundedString(raw)
            Next

            ' 表示単位プレフィックスを決定
            Dim prefix As String = String.Empty
            If 抽出_出庫回数Rb.Checked = True Then prefix = "行数"
            If 抽出_バラRb.Checked = True Then prefix = "バラ数"
            If 抽出_ケースRb.Checked = True Then prefix = "ケース"
            If 抽出_PLRb.Checked = True Then prefix = "PL"
            If 抽出_容積Rb.Checked = True Then prefix = "容積"
            If 抽出_重量Rb.Checked = True Then prefix = "重量"

            ' アイテム数(I1..I5) を抽出して入力欄へ
            抽出I_A1Tb.Text = If(pred.ContainsKey("I1"), pred("I1"), String.Empty)
            抽出I_A2Tb.Text = If(pred.ContainsKey("I2"), pred("I2"), String.Empty)
            抽出I_BTb.Text = If(pred.ContainsKey("I3"), pred("I3"), String.Empty)
            抽出I_CTb.Text = If(pred.ContainsKey("I4"), pred("I4"), String.Empty)
            抽出I_DTb.Text = If(pred.ContainsKey("I5"), pred("I5"), String.Empty)

            ' 出荷先数(E1..E5) を抽出して入力欄へ
            抽出E_A1Tb.Text = If(pred.ContainsKey("E1"), pred("E1"), String.Empty)
            抽出E_A2Tb.Text = If(pred.ContainsKey("E2"), pred("E2"), String.Empty)
            抽出E_BTb.Text = If(pred.ContainsKey("E3"), pred("E3"), String.Empty)
            抽出E_CTb.Text = If(pred.ContainsKey("E4"), pred("E4"), String.Empty)
            抽出E_DTb.Text = If(pred.ContainsKey("E5"), pred("E5"), String.Empty)

            ' 出荷先数/アイテム数 を抽出して入力欄へ
            出荷先数Tb.Text = If(pred.ContainsKey("出荷先数"), pred("出荷先数"), If(pred.ContainsKey("発送先数"), pred("発送先数"), 出荷先数Tb.Text))
            アイテム数Tb.Text = If(pred.ContainsKey("アイテム数"), pred("アイテム数"), アイテム数Tb.Text)

            ' 抽出I_計Tb に I1〜I5 の合計を設定
            Dim sumI As Long = 0
            sumI += ToLongOrZero(抽出I_A1Tb.Text)
            sumI += ToLongOrZero(抽出I_A2Tb.Text)
            sumI += ToLongOrZero(抽出I_BTb.Text)
            sumI += ToLongOrZero(抽出I_CTb.Text)
            sumI += ToLongOrZero(抽出I_DTb.Text)
            抽出I_計Tb.Text = sumI.ToString()

            ' 抽出E_計Tb に E1〜E5 の合計を設定
            Dim sumE As Long = 0
            sumE += ToLongOrZero(抽出E_A1Tb.Text)
            sumE += ToLongOrZero(抽出E_A2Tb.Text)
            sumE += ToLongOrZero(抽出E_BTb.Text)
            sumE += ToLongOrZero(抽出E_CTb.Text)
            sumE += ToLongOrZero(抽出E_DTb.Text)
            抽出E_計Tb.Text = sumE.ToString()

            ' マトリクス表の埋め込み
            If AI予測EIQマトリクス表Dgv Is Nothing Then Return

            Const dataRows As Integer = 5
            Const dataCols As Integer = 5

            ' 行数を確保（データ行 + 合計行）
            While AI予測EIQマトリクス表Dgv.Rows.Count < dataRows + 1
                AI予測EIQマトリクス表Dgv.Rows.Add()
            End While

            Dim lastColIndex As Integer = AI予測EIQマトリクス表Dgv.Columns.Count - 1
            Dim totalsRowIndex As Integer = dataRows ' 下段

            ' 25セルを一度の辞書参照で埋める
            For r As Integer = 0 To dataRows - 1
                For c As Integer = 1 To dataCols
                    Dim idx As Integer = r * dataCols + (c - 1)
                    Dim k As Integer = idx + 1
                    Dim fieldName As String = prefix & k.ToString("D2")
                    Dim value As String = String.Empty
                    If pred.ContainsKey(fieldName) Then value = pred(fieldName)
                    AI予測EIQマトリクス表Dgv.Rows(r).Cells(c).Value = value
                Next
            Next

            ' 横計（各行）
            For r As Integer = 0 To dataRows - 1
                Dim rowSum As Long = 0
                For c As Integer = 1 To dataCols
                    rowSum += ToLongOrZero(If(AI予測EIQマトリクス表Dgv.Rows(r).Cells(c).Value, String.Empty).ToString())
                Next
                AI予測EIQマトリクス表Dgv.Rows(r).Cells(lastColIndex).Value = rowSum.ToString()
            Next

            ' 縦計（各列）
            For c As Integer = 1 To dataCols
                Dim colSum As Long = 0
                For r As Integer = 0 To dataRows - 1
                    colSum += ToLongOrZero(If(AI予測EIQマトリクス表Dgv.Rows(r).Cells(c).Value, String.Empty).ToString())
                Next
                AI予測EIQマトリクス表Dgv.Rows(totalsRowIndex).Cells(c).Value = colSum.ToString()
            Next

            ' 最下行左のラベルと総合計
            AI予測EIQマトリクス表Dgv.Rows(totalsRowIndex).Cells(0).Value = "横計"
            Dim grandTotal As Long = 0
            For c As Integer = 1 To dataCols
                grandTotal += ToLongOrZero(If(AI予測EIQマトリクス表Dgv.Rows(totalsRowIndex).Cells(c).Value, String.Empty).ToString())
            Next
            AI予測EIQマトリクス表Dgv.Rows(totalsRowIndex).Cells(lastColIndex).Value = grandTotal.ToString()

        Catch ex As Exception
            ' 例外は無視（UIで表示したい場合はここでログ等）
        End Try
    End Sub

    ' ========================================
    ' AI予測EIQマトリクス表Dgvのヘッダー初期化
    ' ========================================
    Private Sub AI予測EIQマトリクス表Dgv初期化()
        ' マトリクス表の列ヘッダーを設定
        AI予測EIQマトリクス表Dgv.Columns.Clear()
        AI予測EIQマトリクス表Dgv.Columns.Add("GPLEランク", "GPLEランク")
        AI予測EIQマトリクス表Dgv.Columns.Add("GPLI_A1", "GPLI_A1")
        AI予測EIQマトリクス表Dgv.Columns.Add("GPLI_A2", "GPLI_A2")
        AI予測EIQマトリクス表Dgv.Columns.Add("GPLI_B", "GPLI_B")
        AI予測EIQマトリクス表Dgv.Columns.Add("GPLI_C", "GPLI_C")
        AI予測EIQマトリクス表Dgv.Columns.Add("GPLI_D", "GPLI_D")
        AI予測EIQマトリクス表Dgv.Columns.Add("横計", "横計")

        ' 列幅を調整
        AI予測EIQマトリクス表Dgv.Columns("GPLEランク").Width = 90
        AI予測EIQマトリクス表Dgv.Columns("GPLI_A1").Width = 80
        AI予測EIQマトリクス表Dgv.Columns("GPLI_A2").Width = 80
        AI予測EIQマトリクス表Dgv.Columns("GPLI_B").Width = 80
        AI予測EIQマトリクス表Dgv.Columns("GPLI_C").Width = 80
        AI予測EIQマトリクス表Dgv.Columns("GPLI_D").Width = 80
        AI予測EIQマトリクス表Dgv.Columns("横計").Width = 90


        ' 0列( GPLEランク ) の0行〜4行にラベルを設定
        Dim labels As String() = {"GPLE_A1", "GPLE_A2", "GPLE_B", "GPLE_C", "GPLE_D"}
        AI予測EIQマトリクス表Dgv.Rows.Clear()
        For Each lbl As String In labels
            AI予測EIQマトリクス表Dgv.Rows.Add(New Object() {lbl, String.Empty, String.Empty, String.Empty, String.Empty, String.Empty, String.Empty})
        Next

        ' すべて右寄せに設定（ヘッダーとセル）
        AI予測EIQマトリクス表Dgv.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
        AI予測EIQマトリクス表Dgv.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
        For Each col As DataGridViewColumn In AI予測EIQマトリクス表Dgv.Columns
            col.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
            col.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight
        Next

        ' 読み取り専用にする
        AI予測EIQマトリクス表Dgv.ReadOnly = True
        ' 行ヘッダーを非表示にする
        AI予測EIQマトリクス表Dgv.RowHeadersVisible = False
    End Sub

    ' ========================================
    ' GPLI_A1..GPLI_D のヘッダーを設定
    ' ========================================
    Private Sub GPLI_A1_D_ヘッダー設定()
        ' GPLI_A1..D列のヘッダーを設定
        予測結果Dgv.Columns("GPLI_A1").HeaderText = "ヘッダーA1"
        予測結果Dgv.Columns("GPLI_A2").HeaderText = "ヘッダーA2"
        予測結果Dgv.Columns("GPLI_A3").HeaderText = "ヘッダーA3"
        予測結果Dgv.Columns("GPLI_A4").HeaderText = "ヘッダーA4"
        予測結果Dgv.Columns("GPLI_A5").HeaderText = "ヘッダーA5"
        予測結果Dgv.Columns("GPLI_B1").HeaderText = "ヘッダーB1"
        予測結果Dgv.Columns("GPLI_B2").HeaderText = "ヘッダーB2"
        予測結果Dgv.Columns("GPLI_B3").HeaderText = "ヘッダーB3"
        予測結果Dgv.Columns("GPLI_B4").HeaderText = "ヘッダーB4"
        予測結果Dgv.Columns("GPLI_B5").HeaderText = "ヘッダーB5"
        予測結果Dgv.Columns("GPLI_C1").HeaderText = "ヘッダーC1"
        予測結果Dgv.Columns("GPLI_C2").HeaderText = "ヘッダーC2"
        予測結果Dgv.Columns("GPLI_C3").HeaderText = "ヘッダーC3"
        予測結果Dgv.Columns("GPLI_C4").HeaderText = "ヘッダーC4"
        予測結果Dgv.Columns("GPLI_C5").HeaderText = "ヘッダーC5"
        予測結果Dgv.Columns("GPLI_D1").HeaderText = "ヘッダーD1"
        予測結果Dgv.Columns("GPLI_D2").HeaderText = "ヘッダーD2"
        予測結果Dgv.Columns("GPLI_D3").HeaderText = "ヘッダーD3"
        予測結果Dgv.Columns("GPLI_D4").HeaderText = "ヘッダーD4"
        予測結果Dgv.Columns("GPLI_D5").HeaderText = "ヘッダーD5"
    End Sub
#End Region

＃Region "AI予測従来計算E"

    Private Sub AI予測従来計算E（)
        ' AI予測従来計算出荷先Dgv の列を設定
        AI予測従来計算出荷先Dgv.Columns.Clear()
        AI予測従来計算出荷先Dgv.Columns.Add("出荷日", "出荷日")
        AI予測従来計算出荷先Dgv.Columns.Add("ランク", "ランク")
        AI予測従来計算出荷先Dgv.Columns.Add("出荷先数", "出荷先数")
        AI予測従来計算出荷先Dgv.Columns.Add("出荷回数", "出荷回数")
        AI予測従来計算出荷先Dgv.Columns.Add("バラ数", "バラ数")
        AI予測従来計算出荷先Dgv.Columns.Add("ケース換算", "ケース換算")
        AI予測従来計算出荷先Dgv.Columns.Add("PL換算", "PL換算")
        AI予測従来計算出荷先Dgv.Columns.Add("容積換算", "容積換算")
        AI予測従来計算出荷先Dgv.Columns.Add("重量換算", "重量換算")

        ' 列幅を調整
        For Each col As DataGridViewColumn In AI予測従来計算出荷先Dgv.Columns
            col.Width = 80
        Next
        AI予測従来計算出荷先Dgv.Columns("出荷日").Width = 90

        ' 右寄せ（出荷日・ランク以外）
        For Each col As DataGridViewColumn In AI予測従来計算出荷先Dgv.Columns
            col.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
            col.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
        Next

        ' 読み取り専用
        AI予測従来計算出荷先Dgv.ReadOnly = True
        AI予測従来計算出荷先Dgv.AllowUserToAddRows = False
        AI予測従来計算出荷先Dgv.RowHeadersVisible = False

        ' 予測結果を辞書にキャッシュ
        If 予測結果Dgv Is Nothing OrElse 予測結果Dgv.Rows Is Nothing OrElse 予測結果Dgv.Rows.Count = 0 Then Return

        Dim pred As New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
        For Each r As DataGridViewRow In 予測結果Dgv.Rows
            If r.IsNewRow Then Continue For
            Dim key = If(r.Cells(0).Value, String.Empty).ToString()
            Dim raw = If(r.Cells(1).Value, String.Empty).ToString()
            If String.IsNullOrEmpty(key) Then Continue For
            pred(key) = ToRoundedString(raw)
        Next

        ' GPLEランク（E1..E5）ごとに1行ずつデータを設定
        Dim ranks As String() = {"GPLE_A1", "GPLE_A2", "GPLE_B", "GPLE_C", "GPLE_D"}
        Dim eKeys As String() = {"E1", "E2", "E3", "E4", "E5"}
        ' 各列に対応するプレフィックス
        Dim prefixes As String() = {"行数", "バラ数", "ケース", "PL", "容積", "重量"}

        AI予測従来計算出荷先Dgv.Rows.Clear()
        For i As Integer = 0 To 4
            Dim eVal As String = If(pred.ContainsKey(eKeys(i)), pred(eKeys(i)), String.Empty)

            ' 各プレフィックスごとに (i*5+1)〜(i*5+5) の合計を算出
            Dim sums(prefixes.Length - 1) As Long
            For p As Integer = 0 To prefixes.Length - 1
                For j As Integer = 1 To 5
                    Dim fld As String = prefixes(p) & (i * 5 + j).ToString("D2")
                    If pred.ContainsKey(fld) Then sums(p) += ToLongOrZero(pred(fld))
                Next
            Next

            AI予測従来計算出荷先Dgv.Rows.Add(New Object() {
                "全データ平均",
                ranks(i),
                eVal,
                sums(0).ToString(),
                sums(1).ToString(),
                sums(2).ToString(),
                sums(3).ToString(),
                sums(4).ToString(),
                sums(5).ToString()
            })
        Next

        ' 合計行
        Dim colNames As String() = {"出荷先数", "出荷回数", "バラ数", "ケース換算", "PL換算", "容積換算", "重量換算"}
        Dim totals(colNames.Length - 1) As Long
        For i As Integer = 0 To 4
            For c As Integer = 0 To colNames.Length - 1
                totals(c) += ToLongOrZero(If(AI予測従来計算出荷先Dgv.Rows(i).Cells(colNames(c)).Value, String.Empty).ToString())
            Next
        Next
        AI予測従来計算出荷先Dgv.Rows.Add(New Object() {
            String.Empty,
            "計",
            totals(0).ToString(),
            totals(1).ToString(),
            totals(2).ToString(),
            totals(3).ToString(),
            totals(4).ToString(),
            totals(5).ToString(),
            totals(6).ToString()
        })
    End Sub

    Sub AI予測従来計算I()
        ' AI予測従来計算アイテムDgv の列を設定
        AI予測従来計算アイテムDgv.Columns.Clear()
        AI予測従来計算アイテムDgv.Columns.Add("出荷日", "出荷日")
        AI予測従来計算アイテムDgv.Columns.Add("ランク", "ランク")
        AI予測従来計算アイテムDgv.Columns.Add("アイテム数", "アイテム数")
        AI予測従来計算アイテムDgv.Columns.Add("出荷回数", "出荷回数")
        AI予測従来計算アイテムDgv.Columns.Add("バラ数", "バラ数")
        AI予測従来計算アイテムDgv.Columns.Add("ケース換算", "ケース換算")
        AI予測従来計算アイテムDgv.Columns.Add("PL換算", "PL換算")
        AI予測従来計算アイテムDgv.Columns.Add("容積換算", "容積換算")
        AI予測従来計算アイテムDgv.Columns.Add("重量換算", "重量換算")

        ' 列幅を調整
        For Each col As DataGridViewColumn In AI予測従来計算アイテムDgv.Columns
            col.Width = 80
        Next
        AI予測従来計算アイテムDgv.Columns("出荷日").Width = 90

        ' 右寄せ
        For Each col As DataGridViewColumn In AI予測従来計算アイテムDgv.Columns
            col.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
            col.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
        Next

        ' 読み取り専用
        AI予測従来計算アイテムDgv.ReadOnly = True
        AI予測従来計算アイテムDgv.AllowUserToAddRows = False
        AI予測従来計算アイテムDgv.RowHeadersVisible = False

        ' 予測結果を辞書にキャッシュ
        If 予測結果Dgv Is Nothing OrElse 予測結果Dgv.Rows Is Nothing OrElse 予測結果Dgv.Rows.Count = 0 Then Return

        Dim pred As New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
        For Each r As DataGridViewRow In 予測結果Dgv.Rows
            If r.IsNewRow Then Continue For
            Dim key = If(r.Cells(0).Value, String.Empty).ToString()
            Dim raw = If(r.Cells(1).Value, String.Empty).ToString()
            If String.IsNullOrEmpty(key) Then Continue For
            pred(key) = ToRoundedString(raw)
        Next

        ' GPLIランク（I1..I5）ごとに1行ずつデータを設定
        Dim ranks As String() = {"GPLI_A1", "GPLI_A2", "GPLI_B", "GPLI_C", "GPLI_D"}
        Dim iKeys As String() = {"I1", "I2", "I3", "I4", "I5"}
        ' 各列に対応するプレフィックス
        Dim prefixes As String() = {"行数", "バラ数", "ケース", "PL", "容積", "重量"}

        AI予測従来計算アイテムDgv.Rows.Clear()
        For j As Integer = 0 To 4
            Dim iVal As String = If(pred.ContainsKey(iKeys(j)), pred(iKeys(j)), String.Empty)

            ' 各プレフィックスごとに列方向の合計を算出
            ' I列j の値 = (j+1), (j+1+5), (j+1+10), (j+1+15), (j+1+20)
            Dim sums(prefixes.Length - 1) As Long
            For p As Integer = 0 To prefixes.Length - 1
                For k As Integer = 0 To 4
                    Dim fld As String = prefixes(p) & (j + 1 + k * 5).ToString("D2")
                    If pred.ContainsKey(fld) Then sums(p) += ToLongOrZero(pred(fld))
                Next
            Next

            AI予測従来計算アイテムDgv.Rows.Add(New Object() {
                "全データ平均",
                ranks(j),
                iVal,
                sums(0).ToString(),
                sums(1).ToString(),
                sums(2).ToString(),
                sums(3).ToString(),
                sums(4).ToString(),
                sums(5).ToString()
            })
        Next

        ' 合計行
        Dim colNames As String() = {"アイテム数", "出荷回数", "バラ数", "ケース換算", "PL換算", "容積換算", "重量換算"}
        Dim totals(colNames.Length - 1) As Long
        For i As Integer = 0 To 4
            For c As Integer = 0 To colNames.Length - 1
                totals(c) += ToLongOrZero(If(AI予測従来計算アイテムDgv.Rows(i).Cells(colNames(c)).Value, String.Empty).ToString())
            Next
        Next
        AI予測従来計算アイテムDgv.Rows.Add(New Object() {
            String.Empty,
            "縦計",
            totals(0).ToString(),
            totals(1).ToString(),
            totals(2).ToString(),
            totals(3).ToString(),
            totals(4).ToString(),
            totals(5).ToString(),
            totals(6).ToString()
        })
    End Sub
#End Region
＃Region "AI予測と実計算比較Tp 作成"




    Private Sub 予測計算Gb作成()
        ' 表示単位の取得（表示チェック2Gbのラジオボタン）
        Dim prefix As String = ""
        If 抽出_出庫回数2Rb.Checked Then prefix = "行数"
        If 抽出_バラ2Rb.Checked Then prefix = "バラ数"
        If 抽出_ケース2Rb.Checked Then prefix = "ケース"
        If 抽出_PL2Rb.Checked Then prefix = "PL"
        If 抽出_容積2Rb.Checked Then prefix = "容積"
        If 抽出_重量2Rb.Checked Then prefix = "重量"

        ' 予測結果Dgvから値を取得
        Dim pred As New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
        If 予測結果Dgv Is Nothing OrElse 予測結果Dgv.Rows Is Nothing OrElse 予測結果Dgv.Rows.Count = 0 Then
            ' クリア
            TextBox16.Text = ""
            TextBox10.Text = ""
            TextBox15.Text = ""
            TextBox11.Text = ""
            TextBox4.Text = ""
            TextBox9.Text = ""
            TextBox6.Text = ""
            TextBox13.Text = ""
            TextBox14.Text = ""
            TextBox8.Text = ""
            TextBox3.Text = ""
            TextBox5.Text = ""
            AI予測EIQマトリクス表_複写Dgv.Rows.Clear()
            Return
        End If
        For Each r As DataGridViewRow In 予測結果Dgv.Rows
            If r.IsNewRow Then Continue For
            Dim key = If(r.Cells(0).Value, "").ToString()
            Dim raw = If(r.Cells(1).Value, "").ToString()
            If String.IsNullOrEmpty(key) Then Continue For
            pred(key) = ToRoundedString(raw)
        Next

        ' アイテム数(I1..I5)
        TextBox16.Text = If(pred.ContainsKey("I1"), pred("I1"), "")
        TextBox10.Text = If(pred.ContainsKey("I2"), pred("I2"), "")
        TextBox15.Text = If(pred.ContainsKey("I3"), pred("I3"), "")
        TextBox11.Text = If(pred.ContainsKey("I4"), pred("I4"), "")
        TextBox4.Text = If(pred.ContainsKey("I5"), pred("I5"), "")

        ' 出荷先数(E1..E5)
        TextBox6.Text = If(pred.ContainsKey("E1"), pred("E1"), "")
        TextBox13.Text = If(pred.ContainsKey("E2"), pred("E2"), "")
        TextBox14.Text = If(pred.ContainsKey("E3"), pred("E3"), "")
        TextBox8.Text = If(pred.ContainsKey("E4"), pred("E4"), "")
        TextBox3.Text = If(pred.ContainsKey("E5"), pred("E5"), "")

        ' 合計
        Dim sumI As Long = 0
        sumI += ToLongOrZero(TextBox16.Text)
        sumI += ToLongOrZero(TextBox10.Text)
        sumI += ToLongOrZero(TextBox15.Text)
        sumI += ToLongOrZero(TextBox11.Text)
        sumI += ToLongOrZero(TextBox4.Text)
        TextBox9.Text = sumI.ToString()

        Dim sumE As Long = 0
        sumE += ToLongOrZero(TextBox6.Text)
        sumE += ToLongOrZero(TextBox13.Text)
        sumE += ToLongOrZero(TextBox14.Text)
        sumE += ToLongOrZero(TextBox8.Text)
        sumE += ToLongOrZero(TextBox3.Text)
        TextBox5.Text = sumE.ToString()

        ' マトリクス表の埋め込み
        If AI予測EIQマトリクス表_複写Dgv Is Nothing Then Return
        AI予測EIQマトリクス表_複写Dgv.Rows.Clear()
        AI予測EIQマトリクス表_複写Dgv.Columns.Clear()
        ' 列ヘッダー
        AI予測EIQマトリクス表_複写Dgv.Columns.Add("GPLEランク", "GPLEランク")
        AI予測EIQマトリクス表_複写Dgv.Columns.Add("GPLI_A1", "GPLI_A1")
        AI予測EIQマトリクス表_複写Dgv.Columns.Add("GPLI_A2", "GPLI_A2")
        AI予測EIQマトリクス表_複写Dgv.Columns.Add("GPLI_B", "GPLI_B")
        AI予測EIQマトリクス表_複写Dgv.Columns.Add("GPLI_C", "GPLI_C")
        AI予測EIQマトリクス表_複写Dgv.Columns.Add("GPLI_D", "GPLI_D")
        AI予測EIQマトリクス表_複写Dgv.Columns.Add("横計", "横計")
        ' 列幅を0列は85、その他は83に設定
        For i As Integer = 0 To AI予測EIQマトリクス表_複写Dgv.Columns.Count - 1
            If i = 0 Then
                AI予測EIQマトリクス表_複写Dgv.Columns(i).Width = 90
            Else
                AI予測EIQマトリクス表_複写Dgv.Columns(i).Width = 83
            End If
        Next
        ' 行ラベル
        Dim labels As String() = {"GPLE_A1", "GPLE_A2", "GPLE_B", "GPLE_C", "GPLE_D"}
        For Each lbl As String In labels
            AI予測EIQマトリクス表_複写Dgv.Rows.Add(New Object() {lbl, "", "", "", "", "", ""})
        Next
        ' データ埋め込み
        Const dataRows As Integer = 5
        Const dataCols As Integer = 5
        While AI予測EIQマトリクス表_複写Dgv.Rows.Count < dataRows + 1
            AI予測EIQマトリクス表_複写Dgv.Rows.Add()
        End While
        Dim lastColIndex As Integer = AI予測EIQマトリクス表_複写Dgv.Columns.Count - 1
        Dim totalsRowIndex As Integer = dataRows
        For r As Integer = 0 To dataRows - 1
            For c As Integer = 1 To dataCols
                Dim idx As Integer = r * dataCols + (c - 1)
                Dim k As Integer = idx + 1
                Dim fieldName As String = prefix & k.ToString("D2")
                Dim value As String = ""
                If pred.ContainsKey(fieldName) Then value = pred(fieldName)
                AI予測EIQマトリクス表_複写Dgv.Rows(r).Cells(c).Value = value
            Next
        Next
        ' 横計
        For r As Integer = 0 To dataRows - 1
            Dim rowSum As Long = 0
            For c As Integer = 1 To dataCols
                rowSum += ToLongOrZero(If(AI予測EIQマトリクス表_複写Dgv.Rows(r).Cells(c).Value, "").ToString())
            Next
            AI予測EIQマトリクス表_複写Dgv.Rows(r).Cells(lastColIndex).Value = rowSum.ToString()
        Next
        ' 縦計
        For c As Integer = 1 To dataCols
            Dim colSum As Long = 0
            For r As Integer = 0 To dataRows - 1
                colSum += ToLongOrZero(If(AI予測EIQマトリクス表_複写Dgv.Rows(r).Cells(c).Value, "").ToString())
            Next
            AI予測EIQマトリクス表_複写Dgv.Rows(totalsRowIndex).Cells(c).Value = colSum.ToString()
        Next
        ' 最下行左のラベルと総合計
        AI予測EIQマトリクス表_複写Dgv.Rows(totalsRowIndex).Cells(0).Value = "縦計"
        Dim grandTotal As Long = 0
        For c As Integer = 1 To dataCols
            grandTotal += ToLongOrZero(If(AI予測EIQマトリクス表_複写Dgv.Rows(totalsRowIndex).Cells(c).Value, "").ToString())
        Next
        AI予測EIQマトリクス表_複写Dgv.Rows(totalsRowIndex).Cells(lastColIndex).Value = grandTotal.ToString()
        ' 書式
        AI予測EIQマトリクス表_複写Dgv.ReadOnly = True
        AI予測EIQマトリクス表_複写Dgv.RowHeadersVisible = False
    End Sub


#End Region









End Class
